1 module unit_threaded.randomized.gen;
2 
3 template from(string moduleName) {
4     mixin("import from = " ~ moduleName ~ ";");
5 }
6 
7 /* Return $(D true) if the passed $(D T) is a $(D Gen) struct.
8 
9 A $(D Gen!T) is something that implicitly converts to $(D T), has a method
10 called $(D gen) that is accepting a $(D ref Random).
11 
12 This module already brings Gens for numeric types, strings and ascii strings.
13 
14 If a function needs to be benchmarked that has a parameter of custom type a
15 custom $(D Gen) is required.
16 */
17 template isGen(T) {
18     static if (is(T : Gen!(S), S...))
19         enum isGen = true;
20     else
21         enum isGen = false;
22 }
23 
24 ///
25 unittest {
26     static assert(!isGen!int);
27     static assert(isGen!(Gen!(int, 0, 10)));
28 }
29 
30 private template minimum(T) {
31     import std.traits : isIntegral, isFloatingPoint, isSomeChar;
32 
33     static if (isIntegral!T || isSomeChar!T)
34         enum minimum = T.min;
35     else static if (isFloatingPoint!T)
36         enum mininum = T.min_normal;
37     else
38         enum minimum = T.init;
39 }
40 
41 private template maximum(T) {
42     import std.traits : isNumeric;
43 
44     static if (isNumeric!T)
45         enum maximum = T.max;
46     else
47         enum maximum = T.init;
48 }
49 
50 /** A $(D Gen) type that generates numeric values between the values of the
51 template parameter $(D low) and $(D high).
52 */
53 mixin template GenNumeric(T, T low, T high) {
54 
55     import std.random : Random;
56 
57     static assert(is(typeof(() { T[] res = frontLoaded(); })),
58             "GenNumeric needs a function frontLoaded returning " ~ T.stringof ~ "[]");
59 
60     alias Value = T;
61 
62     T value;
63 
64     T gen(ref Random gen) {
65         import std.random : uniform;
66 
67         static assert(low <= high);
68 
69         this.value = _index < frontLoaded.length ? frontLoaded[_index++]
70             : uniform!("[]")(low, high, gen);
71 
72         return this.value;
73     }
74 
75     ref T opCall() {
76         return this.value;
77     }
78 
79     void toString(scope void delegate(const(char)[]) sink) @trusted {
80         import std.format : formattedWrite;
81         import std.traits : isFloatingPoint;
82 
83         static if (isFloatingPoint!T) {
84             static if (low == T.min_normal && high == T.max) {
85                 formattedWrite(sink, "'%s'", this.value);
86             }
87         } else static if (low == T.min && high == T.max) {
88             formattedWrite(sink, "'%s'", this.value);
89         } else {
90             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, low, high);
91         }
92     }
93 
94     alias opCall this;
95 
96     private int _index;
97 }
98 
99 /** A $(D Gen) type that generates numeric values between the values of the
100 template parameter $(D low) and $(D high).
101 */
102 struct Gen(T, T low = minimum!T, T high = maximum!T)
103         if (from!"std.traits".isIntegral!T) {
104     private static T[] frontLoaded() @safe pure nothrow {
105         import std.algorithm : filter;
106         import std.array : array;
107 
108         T[] values = [0, 1, T.min, T.max];
109         return values.filter!(a => a >= low && a <= high).array;
110     }
111 
112     mixin GenNumeric!(T, low, high);
113 }
114 
115 struct Gen(T, T low = 0, T high = 6.022E23)
116         if (from!"std.traits".isFloatingPoint!T) {
117     private static T[] frontLoaded() @safe pure nothrow {
118         import std.algorithm : filter;
119         import std.array : array;
120 
121         T[] values = [0, T.epsilon, T.min_normal, high];
122         return values.filter!(a => a >= low && a <= high).array;
123     }
124 
125     mixin GenNumeric!(T, low, high);
126 }
127 
128 /** A $(D Gen) type that generates ASCII strings with a number of
129 characters that is between template parameter $(D low) and $(D high).
130 
131 If $(D low) and $(D high) are very close together, this might return
132 values that are too short. They should differ by at least three for
133 char strings, one for wstrings, and zero for dstrings.
134 */
135 struct Gen(T, size_t low = 0, size_t high = 32)
136         if (from!"std.traits".isSomeString!T) {
137     static const dchar[] charset;
138     import std.random : Random, uniform;
139 
140     static immutable size_t numCharsInCharSet;
141     alias Value = T;
142 
143     T value;
144     static this() {
145         import std.array : array;
146         import std.uni : unicode;
147         import std.format : format;
148         import std.range : chain, iota;
149         import std.algorithm : map, joiner;
150         import std.conv : to;
151         import std.utf : count;
152 
153         charset = chain( // \t and \n
154                 iota(0x09, 0x0B), // \r
155                 iota(0x0D, 0x0E), // ' ' through '~'; next is DEL
156                 iota(0x20, 0x7F), // Vulgar fractions, punctuation, letters with accents, Greek
157                 iota(0xA1,
158                     0x377), // More Greek
159                 iota(0x37A, 0x37F), iota(0x384, 0x38A), iota(0x38C,
160                     0x38C), iota(0x38E, 0x3A1), // Greek, Cyrillic, a bit of Armenian
161                 iota(0x3A3, 0x52F), // Armenian
162                 iota(0x531,
163                     0x556), iota(0x559, 0x55F), // Arabic
164                 iota(0xFBD3, 0xFD3F),
165                 iota(0xFD50, 0xFD8F), iota(0xFD92, 0xFDC7), // Linear B, included because it's a high character set
166                 iota(0x1003C, 0x1003D),
167                 iota(0x1003F, 0x1004D), iota(0x10050, 0x1005D), iota(0x10080,
168                     0x100FA), // Emoji
169                 iota(0x1F300, 0x1F6D4)).map!(a => cast(dchar) a).array;
170         numCharsInCharSet = charset.length;
171     }
172 
173     T gen(ref Random gen) {
174         static assert(low <= high);
175         import std.range.primitives : ElementType;
176         import std.array : appender;
177         import std.utf : encode;
178 
179         if (_index < frontLoaded.length) {
180             value = frontLoaded[_index++];
181             return value;
182         }
183 
184         auto app = appender!T();
185         app.reserve(high);
186         size_t numElems = uniform!("[]")(low, high, gen);
187         static if ((ElementType!T).sizeof == 1) {
188             char[4] buf;
189         } else static if ((ElementType!T).sizeof == 2) {
190             wchar[2] buf;
191         } else {
192             dchar[1] buf;
193         }
194 
195         size_t appLength = 0;
196         while (appLength < numElems) {
197             size_t charIndex = uniform!("[)")(0, charset.length, gen);
198             auto len = encode(buf, charset[charIndex]);
199             appLength += len;
200             if (appLength > high)
201                 break;
202             app.put(buf[0 .. len]);
203         }
204 
205         this.value = app.data;
206         return this.value;
207     }
208 
209     ref T opCall() {
210         return this.value;
211     }
212 
213     void toString(scope void delegate(const(char)[]) sink) {
214         import std.format : formattedWrite;
215 
216         static if (low == 0 && high == 32) {
217             formattedWrite(sink, "'%s'", this.value);
218         } else {
219             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, low, high);
220         }
221     }
222 
223     alias opCall this;
224 
225 private:
226 
227     int _index;
228 
229     T[] frontLoaded() @safe pure nothrow const {
230         import std.algorithm : filter;
231         import std.array : array;
232 
233         T[] values = ["", "a", "é"];
234         return values.filter!(a => a.length >= low && a.length <= high).array;
235     }
236 }
237 
238 /// DITTO This random $(D string)s only consisting of ASCII character
239 struct GenASCIIString(size_t low = 1, size_t high = 32) {
240     import std.random : Random;
241 
242     static string charSet;
243     static immutable size_t numCharsInCharSet;
244 
245     string value;
246 
247     static this() {
248         import std.array : array;
249         import std.uni : unicode;
250         import std.format : format;
251         import std.range : chain, iota;
252         import std.algorithm : map, joiner;
253         import std.conv : to;
254         import std.utf : byDchar, count;
255 
256         GenASCIIString!(low, high).charSet = to!string(chain(iota(0x21, 0x7E)
257                 .map!(a => to!char(cast(dchar) a)).array));
258 
259         GenASCIIString!(low, high).numCharsInCharSet = count(charSet);
260     }
261 
262     string gen(ref Random gen) {
263         import std.array : appender;
264         import std.random : uniform;
265 
266         if (_index < frontLoaded.length) {
267             value = frontLoaded[_index++];
268             return value;
269         }
270 
271         auto app = appender!string();
272         app.reserve(high);
273         size_t numElems = uniform!("[]")(low, high, gen);
274 
275         for (size_t i = 0; i < numElems; ++i) {
276             size_t toSelect = uniform!("[)")(0, numCharsInCharSet, gen);
277             app.put(charSet[toSelect]);
278         }
279 
280         this.value = app.data;
281         return this.value;
282     }
283 
284     ref string opCall() {
285         return this.value;
286     }
287 
288     void toString(scope void delegate(const(char)[]) sink) {
289         import std.format : formattedWrite;
290 
291         static if (low == 0 && high == 32) {
292             formattedWrite(sink, "'%s'", this.value);
293         } else {
294             formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, low, high);
295         }
296     }
297 
298     alias opCall this;
299 
300 private:
301 
302     int _index;
303 
304     string[] frontLoaded() @safe pure nothrow const {
305         return ["", "a"];
306     }
307 }
308 
309 struct Gen(T, size_t low = 1, size_t high = 1024)
310         if (from!"std.range.primitives".isInputRange!T && !from!"std.traits".isSomeString!T) {
311 
312     import std.traits : Unqual, isIntegral, isFloatingPoint;
313     import std.range : ElementType;
314     import std.random : Random;
315 
316     alias Value = T;
317     alias E = Unqual!(ElementType!T);
318 
319     T value;
320     Gen!E elementGen;
321 
322     T gen(ref Random rnd) {
323         value = _index < frontLoaded.length ? frontLoaded[_index++] : genArray(rnd);
324         return value;
325     }
326 
327     alias value this;
328 
329 private:
330 
331     size_t _index;
332     //these values are always generated
333     T[] frontLoaded() @safe nothrow {
334         T[] ret = [[]];
335         return ret;
336     }
337 
338     T genArray(ref Random rnd) {
339         import std.array : appender;
340         import std.random : uniform;
341 
342         immutable length = uniform(low, high, rnd);
343         auto app = appender!T;
344         app.reserve(length);
345         foreach (i; 0 .. length) {
346             app.put(elementGen.gen(rnd));
347         }
348 
349         return app.data;
350     }
351 }
352 
353 static assert(isGen!(Gen!(int[])));
354 
355 struct Gen(T) if (is(T == bool)) {
356     import std.random : Random;
357 
358     bool value;
359     alias value this;
360 
361     bool gen(ref Random rnd) @safe {
362         import std.random : uniform;
363 
364         value = [false, true][uniform(0, 2, rnd)];
365         return value;
366     }
367 }
368 
369 struct Gen(T, T low = minimum!T, T high = maximum!T)
370         if (from!"std.traits".isSomeChar!T) {
371     private static T[] frontLoaded() @safe pure nothrow {
372         return [];
373     }
374 
375     mixin GenNumeric!(T, low, high);
376 }
377 
378 private template AggregateTuple(T...) {
379     import unit_threaded.randomized.random : ParameterToGen;
380     import std.meta : staticMap;
381 
382     alias AggregateTuple = staticMap!(ParameterToGen, T);
383 }
384 
385 struct Gen(T) if (from!"std.traits".isAggregateType!T) {
386 
387     import std.traits : Fields;
388     import std.random : Random;
389 
390     AggregateTuple!(Fields!T) generators;
391 
392     alias Value = T;
393     Value value;
394 
395     T gen(ref Random rnd) @safe {
396         static if (is(T == class))
397             if (value is null)
398                 value = new T;
399 
400         foreach (i, ref g; generators) {
401             value.tupleof[i] = g.gen(rnd);
402         }
403 
404         return value;
405     }
406 
407     inout(T) opCall() inout {
408         return this.value;
409     }
410 
411     alias opCall this;
412 
413 }